﻿#region Copyright &copy; 2001-2003 Jean-Claude Manoli [jc@manoli.net]

/*
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the author(s) be held liable for any damages arising from
 * the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 *   1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 * 
 *   2. Altered source versions must be plainly marked as such, and must not
 *      be misrepresented as being the original software.
 * 
 *   3. This notice may not be removed or altered from any source distribution.
 */
#endregion

namespace CodeFormatter
{
    using System;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;

    /// <summary>
    /// Provides a base implementation for all code formatters.
    /// </summary>
    /// <remarks>
    /// <para>
    /// To display the formatted code on your web site, the web page must 
    ///        refer to a stylesheet that defines the formatting for the different 
    ///        CSS classes generated by CSharpFormat:
    ///        .csharpcode, pre, .rem, .kwrd, .str, .op, .preproc, .alt, .lnum.
    ///    </para>
    /// <para>
    /// Note that if you have multi-line comments in your source code
    ///        (like /* ... */), the "line numbers" or "alternate line background" 
    ///        options will generate code that is not strictly HTML 4.01 compliant. 
    ///        The code will still look good with IE5+ or Mozilla 0.8+. 
    ///    </para>
    /// </remarks>
    public abstract class SourceFormat
    {
        #region Constants and Fields

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SourceFormat"/> class. 
        /// The source format.
        /// </summary>
        protected SourceFormat()
        {
            this.TabSpaces = 4;
            this.LineNumbers = false;
            this.Alternate = false;

            // _embedStyleSheet = false;
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets a value indicating whether enables or disables alternating line background.
        /// </summary>
        /// <value>
        /// When <b>true</b>, lines background is alternated. 
        ///     The default is <b>false</b>.
        /// </value>
        public bool Alternate { get; set; }

        /// <summary>
        ///     Gets or sets a value indicating whether enables or disables line numbers in output.
        /// </summary>
        /// <value>
        /// When <b>true</b>, line numbers are generated. 
        ///     The default is <b>false</b>.
        /// </value>
        public bool LineNumbers { get; set; }

        /// <summary>
        ///     Gets or sets the tabs width.
        /// </summary>
        /// <value>The number of space characters to substitute for tab 
        ///     characters. The default is <b>4</b>, unless overridden is a 
        /// derived class.</value>
        public byte TabSpaces { get; set; }

        /// <summary>
        ///     Gets or sets the regular expression used to capture language tokens.
        /// </summary>
        protected Regex CodeRegex { get; set; }

        #endregion

        /*
        private bool _embedStyleSheet;
        
        /// <summary>
        /// Enables or disables the embedded CSS style sheet.
        /// </summary>
        /// <value>When <b>true</b>, the CSS &lt;style&gt; element is included 
        /// in the HTML output. The default is <b>false</b>.</value>
        public bool EmbedStyleSheet
        {
            get { return _embedStyleSheet; }
            set { _embedStyleSheet = value; }
        }
        */
        #region Public Methods

        /// <overloads>
        /// Transform source code to HTML 4.01.
        /// </overloads>
        /// <summary>
        /// Transforms a source code stream to HTML 4.01.
        /// </summary>
        /// <param name="source">
        /// Source code stream.
        /// </param>
        /// <returns>
        /// A string containing the HTML formatted code.
        /// </returns>
        public string FormatCode(Stream source)
        {
            var reader = new StreamReader(source);
            var s = reader.ReadToEnd();
            reader.Close();
            return this.FormatCode(s, this.LineNumbers, this.Alternate, false);
        }

        /// <summary>
        /// Transforms a source code string to HTML 4.01.
        /// </summary>
        /// <param name="source">
        /// The source.
        /// </param>
        /// <returns>
        /// A string containing the HTML formatted code.
        /// </returns>
        public string FormatCode(string source)
        {
            return this.FormatCode(source, this.LineNumbers, this.Alternate, false);
        }

        /// <summary>
        /// Allows formatting a part of the code in a different language,
        ///     for example a JavaScript block inside an HTML file.
        /// </summary>
        /// <param name="source">
        /// The source.
        /// </param>
        /// <returns>
        /// The format sub code.
        /// </returns>
        public string FormatSubCode(string source)
        {
            return this.FormatCode(source, false, false, true);
        }

        #endregion

        ///// <summary>
        ///// Gets the CSS stylesheet as a stream.
        ///// </summary>
        ///// <returns>A text <see cref="Stream"/> of the CSS definitions.</returns>
        // public static Stream GetCssStream()
        // {
        // return Assembly.GetExecutingAssembly().GetManifestResourceStream(
        // "CodeFormatter.csharp.css");
        // }

        ///// <summary>
        ///// Gets the CSS stylesheet as a string.
        ///// </summary>
        ///// <returns>A string containing the CSS definitions.</returns>
        // public static string GetCssString()
        // {
        // StreamReader reader = new StreamReader(GetCssStream());
        // return reader.ReadToEnd();
        // }
        #region Methods

        /// <summary>
        /// Called to evaluate the HTML fragment corresponding to each 
        ///     matching token in the code.
        /// </summary>
        /// <param name="match">
        /// The <see cref="Match"/> resulting from a 
        ///     single regular expression match.
        /// </param>
        /// <returns>
        /// A string containing the HTML code fragment.
        /// </returns>
        protected abstract string MatchEval(Match match);

        /// <summary>
        /// Formats the code.
        /// </summary>
        /// <param name="source">
        /// The source.
        /// </param>
        /// <param name="lineNumbers">
        /// The line numbers.
        /// </param>
        /// <param name="alternate">
        /// The alternate.
        /// </param>
        /// <param name="subCode">
        /// The sub code.
        /// </param>
        /// <returns>
        /// The formatted code.
        /// </returns>
        private string FormatCode(string source, bool lineNumbers, bool alternate, bool subCode)
        {
            // replace special characters
            var sb = new StringBuilder(source);

            if (!subCode)
            {
                sb.Replace("&", "&amp;");
                sb.Replace("<", "&lt;");
                sb.Replace(">", "&gt;");
                sb.Replace("\t", string.Empty.PadRight(this.TabSpaces));
            }

            // color the code
            source = this.CodeRegex.Replace(sb.ToString(), new MatchEvaluator(this.MatchEval));

            sb = new StringBuilder();

            // if (embedStyleSheet)
            // {
            // sb.Append("<style type=\"text/css\">\n");
            // sb.Append(GetCssString());
            // sb.Append("</style>\n");
            // }
            if (lineNumbers || alternate)
            {
                // we have to process the code line by line
                if (!subCode)
                {
                    sb.Append("<div class=\"code\">\n");
                }

                using (var reader = new StringReader(source))
                {
                    var i = 0;
                    const string Spaces = "    ";
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        i++;
                        if (alternate && ((i % 2) == 1))
                        {
                            sb.Append("<p class=\"alt\">");
                        }
                        else
                        {
                            sb.Append("<div>");
                        }

                        if (lineNumbers)
                        {
                            var order = (int)Math.Log10(i);
                            sb.AppendFormat("<span class=\"lnum\">{0}{1}:  </span>", Spaces.Substring(0, 3 - order), i);
                        }

                        sb.Append(line.Length == 0 ? "&nbsp;" : line);

                        sb.Append("</div>\n");
                    }
                }

                if (!subCode)
                {
                    sb.Append("</div>");
                }
            }
            else
            {
                // have to use a <pre> because IE below ver 6 does not understand 
                // the "white-space: pre" CSS value
                if (!subCode)
                {
                    sb.Append("<div class=\"code\">\n");
                }

                sb.Append(source);
                if (!subCode)
                {
                    sb.Append("</div>");
                }
            }

            return sb.ToString();
        }

        #endregion
    }
}